package net.chathu.airgw.model.conn 
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.Socket;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.utils.Timer;
	import net.chathu.airgw.Config;
	import net.chathu.airgw.crypto.CipherOutputStream;
	import net.chathu.airgw.crypto.RC4;
	import net.chathu.airgw.crypto.SecuredSocket;
	import net.chathu.airgw.Debug;
	import net.chathu.airgw.model.ChatOptions;
	import net.chathu.airgw.model.handler.ChatPacketProcessor;
	import net.chathu.airgw.model.User;
	import net.chathu.airgw.Soul;
	import net.chathu.airgw.util.ByteArrayUtil;

	/**
	 * ...
	 * @author ze
	 */
	public class DecryptedChatServerConnection implements IChatServerConnection
	{
		
		private var socket:Socket;
		private var host:String;
		private var port:int;
		private var soul:Soul;
		private var readBuffer:ByteArray;
		
		private var securedSocket:SecuredSocket;
		private var timer:Timer;
		public function DecryptedChatServerConnection(soul:Soul,host:String, port:int) 
		{
			this.soul = soul;
			this.host = host;
			this.port = port;
			
			initReadBuffer();
			initConnection();
		}
		
		private function initConnection():Boolean
		{
			if (!socket)
			{
				socket = new Socket();
				socket.addEventListener(Event.CLOSE, onSocketClose);
				socket.addEventListener(Event.CONNECT, onSocketConnect);
				socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIOError);
				socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
				socket.addEventListener(ProgressEvent.SOCKET_DATA, onData);
				socket.connect(host, port);
				
				return true;
			} else {
				return false;
			}
		}
		
		private function disconnect():Boolean
		{
			if (socket)
			{
				if (socket.connected)
				{
					socket.close();
					return true;
				} else {
					return false;
				}
			} else {
				return false;
			}
		}
		
		
		private function onSocketClose(event:Event):void
		{
			trace("CHAT onSocketClose");
			soul.onChatDisconnected();
		}
		
		private function onSocketConnect(event:Event):void
		{
			trace("CHAT onSocketConnect");
		}
		
		private function onSocketIOError(event:Event):void
		{
			trace("CHAT onSocketIOError");
		}
		
		private function onSocketSecurityError(event:Event):void
		{
			trace("onSocketSecurityError");
		}
		
		private var initialized:Boolean = false;
		private function onData(event:Event):void
		{
			//trace("CHAT onChatSever data, len:" + socket.bytesAvailable);
			
			if (!initialized)
			{
				
				initialized = true;
				securedSocket = new SecuredSocket(socket, new RC4(), false);
				var crypt_out:CipherOutputStream = securedSocket.getOutputStream();
				crypt_out.writeInt(0x12345678);
				sendPacket(login_java());
				sendPacket(login_site(soul.sessionData.SID,""));
				

				timer = new Timer(20000);
				timer.addEventListener(TimerEvent.TIMER, onPingNeeded);
				timer.start();
				securedSocket.flush();
				socket.flush();
				
			} else {

				do {
					var gotPacket:Boolean = false;
					if (!securedSocket.connected)
					{
						trace("Socket closed, quit:");
						return;
					}
					//TODO: handle socket close event before try to read!
					if (readBuffer.position == 0 && securedSocket.getInputStream().bytesAvailable>=2)
					{
						//trace("New packet");
						securedSocket.getInputStream().readBytes_BA(readBuffer, 0, 2);
					}
					
					if (readBuffer.position >= 2)
					{
						//mar megvan a packet length
						//ha be tudunk olvasni annyi byte-ot, amennyi a csomag hossza-a mar beolvasott byteok 
						var bytesRead:int = readBuffer.position;
						readBuffer.position = 0;
						var hiByte:int = readBuffer.readUnsignedByte();
						var loByte:int = readBuffer.readUnsignedByte();
						
						
						var bytesNeeded:int = ((hiByte << 8) + loByte) - (bytesRead - 2);
						readBuffer.position = bytesRead;
						if (securedSocket.getInputStream().bytesAvailable >= bytesNeeded)
						{
							securedSocket.getInputStream().readBytes_BA(readBuffer, bytesRead, bytesNeeded);
							//megvan egy uj teljes csomag!
							gotPacket = true;
							
							// parse packet here:
							//ByteArrayDump.dump(readBuffer);
							var packetdata:Array = parsePacketContent(readBuffer);
							
							//owner.ircConnection.sendPacket(new EchoPacket(Config.GW_ECHO_NICK, "Packet received: " + packetdata[0]));
							ChatPacketProcessor.handlePacket(packetdata, soul.sessionData);
							initReadBuffer();
							
						}
					}
				} while (gotPacket);
				//trace("data parsed");
				
				socket.flush();
			}
			
		}
		
		private function initReadBuffer():void {
			readBuffer = new ByteArray();
			readBuffer.endian = Endian.LITTLE_ENDIAN;
		}
		public function login_java():ByteArray
		{
			return ByteArrayUtil.convertParams(["ver", Config.CHAT_CLIENT_VERSION, "Oracle Corporation", "1.7.0-ea", "Windows 7", "6.1"], true);
		}
		
		public function login_site(sid:String, sessionId:String):ByteArray
		{
			//TODO: fix this!..mar reg nem ez az url
			return ByteArrayUtil.convertParams(["auth_sid", sid, "chat", "http://chat.hu/javalogin/login.php?act=java&sessionid=" + sessionId + "&sid=" + sid + "&pictures=1&site=chat"]);
		}
		
		public function parsePacketContent(bytearray:ByteArray):Array
		{
			var currpos:int = bytearray.position;
			bytearray.position = 2;
			var str:String = bytearray.readUTFBytes(bytearray.bytesAvailable);
			var delimstr:String = "\u00c0\u0080";
			trace(">> " + str.split(delimstr).join("|"));
			bytearray.position = currpos;
			
			//trace(delimstr + " SPLIT LEN: "+ str.split(delimstr).length );
			var ret:Array = str.split(delimstr);
			/*for (var s:String in ret)
			{
				trace(ret[s]);
				try 
				{
					var x:String = StringUtil.convertFromISO88591(ret[s]);
					
				}catch (err:Error)
				{
					trace(err.message);
				}
			}*/
			return ret;
		}
		
		
		public function sendPacket(bytearray:ByteArray):void
		{
			try {
				securedSocket.getOutputStream().writeNullByteContainingUTF(bytearray);
				securedSocket.flush();
			} catch (err:Error) {
				//cannot send, disconnected
				timer.stop();
			}
		}
		
		//a latszat neha csal, reszben mert egyuttal garbage collectiont is vegzunk is, plussz...
		private var pingCntr:int = 0;
		protected function onPingNeeded(event:TimerEvent):void {
			sendPacket(ByteArrayUtil.convertParams(["ping"]));
			pingCntr++;
			if (pingCntr % 45 == 0) 
			{
				pingCntr = 0;
				sendPacket(createSaveStateMessage());
			}
			
			//egy remek lehetoseg a torlesre:
			System.gc();
			System.gc();
			
		}
		
		private function createSaveStateMessage():ByteArray {
			var color:String = User(soul.sessionData.chatData.users.getUser(soul.sessionData.chatData.userId)).color;
			var options:ChatOptions = soul.sessionData.chatData.chatOptions;
			return ByteArrayUtil.convertParams([
				"m",
				"userinfo",
				"statall",
					color + 
					'A' +
					options.disturbState + 
					b2s(options.toLower) + 
					b2s(options.tooltip) + 
					b2s(options.newPriv) + 
					b2s(options.privMsg) + 
					b2s(options.newPrivFrame) +
					b2s(options.allowPopups) + 
					b2s(options.fullNick) + 
					b2s(options.filterAtNickList) + 
					b2s(options.allowAwayMsg) + 
					b2s(options.quickUserInfo) + 
					b2s(options.displayTime),
				options.acceptConfRoomInvite,
				options.awayMsg=="" ? "-" : options.awayMsg
			]);
		}
		
		//booleanToString
		private function b2s(bool:Boolean):String {
			if (bool)
			{
				return "1";
			} else {
				return "0";
			}
		}
		
		public function get connected():Boolean {
			//TODO: check this
			return socket.connected; //a titkositott socketrol adjon infot
		}
		
		public function close(writeEndingMessage:Boolean):void {
			if (writeEndingMessage) {
				trace("Chatconnection finish: OUTPUTTHREADDEATH");
				sendPacket(ByteArrayUtil.convertParams(["OUTPUTTHREADDEATH"]));
			}
			socket.close();//nem ezt kell bezarni, hanem a titkositottat			
		}
	}
}